﻿namespace Vibstudio.NetConfigurator.Engine
{
    #region Namespaces

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Xml.Linq;
    using System.Xml.Schema;

    using Vibstudio.NetConfigurator.Model;

    using FileNotFoundException = Vibstudio.NetConfigurator.Exceptions.FileNotFoundException;

    #endregion

    internal static class ConfigurationHelper
    {
        #region Constants

        private const string APPS_FILE_NAME = "applications.xml";

        private const string APPS_SCHEMA_NAME = "applications.xsd";

        private const string CONFIGS_FILE_NAME = "configurations.xml";

        private const string CONFIGS_SCHEMA_NAME = "configurations.xsd";

        private const string BACKUP_FOLDER_NAME = "Backup";

        #endregion

        #region Properties

        internal static string ApplicationsFileName
        {
            get { return APPS_FILE_NAME; }
        }

        /// <summary>
        /// The file of configurations (configurations.xml)
        /// </summary>
        internal static string ConfigurationsFileName
        {
            get { return CONFIGS_FILE_NAME; }
        }

        internal static string ExecutablePath
        {
            get { return Directory.GetCurrentDirectory(); }
        }

        internal static string ApplicationsFilePath
        {
            get { return Path.Combine(Directory.GetCurrentDirectory(), APPS_FILE_NAME); }
        }

        internal static string BackupFolderPath
        {
            get { return Path.Combine(Directory.GetCurrentDirectory(), BACKUP_FOLDER_NAME); }
        }

        internal static string ConfigurationsFilePath
        {
            get { return Path.Combine(Directory.GetCurrentDirectory(), CONFIGS_FILE_NAME); }
        }

        internal static string DateTimeNow
        {
            get { return DateTime.Now.ToString("ddMMyyyy-HHmmss"); }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Get the name of configuration file ([ApplicationName].exe.config, web.config or app.config)
        /// </summary>
        /// <param name="application">The Application object</param>
        /// <returns>Name of configuration file</returns>
        internal static string ConfigurationFileName(Application application)
        {
            string applicationType = Enum.GetName(typeof(ApplicationType), application.Type).ToLower();
            string fileName = application.Type == ApplicationType.Exe
                                  ? String.Format("{0}.{1}", application.ID, applicationType)
                                  : applicationType;

            return String.Concat(fileName, ".config");
        }

        internal static void CopyConfigurationsFileReference(string path, IEnumerable<Application> applications)
        {
            if (!Directory.Exists(BackupFolderPath))
            {
                Directory.CreateDirectory(BackupFolderPath);
            }

            foreach (Application app in applications)
            {
                string sourceFile;

                try
                {
                    sourceFile = GetConfigurationFileReferencePath(path, app);
                }
                catch (FileNotFoundException)
                {
                    sourceFile = string.Empty;
                }

                string destFile = string.Format(@"{0}\{1}_{2}_{3}",
                                                BackupFolderPath,
                                                app.ID,
                                                DateTimeNow,
                                                ConfigurationFileName(app));

                if (File.Exists(sourceFile))
                {
                    File.Copy(sourceFile, destFile, true);
                }
            }
        }

        internal static void CopyConfigurationsFile()
        {
            if (!Directory.Exists(BackupFolderPath))
            {
                Directory.CreateDirectory(BackupFolderPath);
            }

            string applicationsFileSourcePath = Path.Combine(Directory.GetCurrentDirectory(), APPS_FILE_NAME);
            string applicationsFileDestPath = string.Format(@"{0}\{1}_{2}",
                                                            BackupFolderPath,
                                                            DateTimeNow,
                                                            APPS_FILE_NAME);
            if (File.Exists(applicationsFileSourcePath))
            {
                File.Copy(applicationsFileSourcePath, applicationsFileDestPath, true);
            }

            string configurationsFileSourcePath = Path.Combine(Directory.GetCurrentDirectory(), CONFIGS_FILE_NAME);
            string configurationsFileDestPath = string.Format(@"{0}\{1}_{2}",
                                                              BackupFolderPath,
                                                              DateTimeNow,
                                                              CONFIGS_FILE_NAME);
            if (File.Exists(configurationsFileSourcePath))
            {
                File.Copy(configurationsFileSourcePath, configurationsFileDestPath, true);
            }
        }

        internal static IDictionary<Application, List<string>> GetApplicationSettings()
        {
            const string ROOT_NAME = "application";
            string filePath = ApplicationsFilePath;

            var result = new Dictionary<Application, List<string>>(new Application.EqualityComparer());

            XDocument document = XDocument.Load(filePath);

            IEnumerable<XElement> rootElements =
                from e in document.Descendants(ROOT_NAME).Elements("name")
                select e;

            foreach (XElement rootElement in rootElements)
            {
                if (rootElement.Attribute("active").Value.ToLower() == "y")
                {
                    IEnumerable<XElement> nameElements =
                        from e in rootElement.Elements()
                        select e;

                    List<string> values = nameElements.Select(element => element.Value).ToList();

                    var application =
                        new Application
                            {
                                ID = rootElement.Attribute("id").Value,
                                Type = (ApplicationType)
                                       Enum.Parse(typeof(ApplicationType), rootElement.Attribute("type").Value, true)
                            };
                    result.Add(application, values);
                }
            }

            return result;
        }

        internal static IDictionary<string, List<ConfigurationSetting>> GetConfigurationSettings()
        {
            const string ROOT_NAME = "configuration";
            string filePath = ConfigurationsFilePath;

            var result = new Dictionary<string, List<ConfigurationSetting>>();

            XDocument document = XDocument.Load(filePath);

            IEnumerable<XElement> rootElements =
                from e in document.Descendants(ROOT_NAME).Elements("name")
                select e;

            foreach (XElement rootElement in rootElements)
            {
                if (rootElement.Attribute("active") != null)
                {
                    if (rootElement.Attribute("active").Value.ToLower() == "y")
                    {
                        IEnumerable<XElement> nameElements =
                            from e in rootElement.Elements()
                            select e;

                        bool hasDescendants = nameElements.Descendants().Any();
                        if (hasDescendants)
                        {
                            nameElements = nameElements.Descendants();
                        }

                        List<ConfigurationSetting> configurationSettings =
                            (from element in nameElements
                             let name =
                                 hasDescendants ? element.Parent.Name.LocalName : element.Name.LocalName
                             let key =
                                 element.Attribute("key") != null ? element.Attribute("key").Value : string.Empty
                             let value =
                                 element.Attribute("value") != null ? element.Attribute("value").Value : string.Empty
                             select new ConfigurationSetting
                             {
                                 Name = name,
                                 Key = key,
                                 Value = value
                             }).ToList();

                        result.Add(rootElement.Attribute("id").Value, configurationSettings);
                    }
                }
            }

            return result;
        }

        internal static XmlSchemaSet GetApplicationsSchemaSet()
        {
            return GetSchemaSet(APPS_SCHEMA_NAME);
        }

        internal static XmlSchemaSet GetConfigurationsSchemaSet()
        {
            return GetSchemaSet(CONFIGS_SCHEMA_NAME);
        }

        private static XmlSchemaSet GetSchemaSet(string resourceName)
        {
            var schemas = new XmlSchemaSet();
            Assembly assembly = Assembly.GetExecutingAssembly();
            string fullResourceName = string.Format("{0}.Schemas.{1}", assembly.FullName.Split(',')[0], resourceName);
            using (Stream schemaStream = assembly.GetManifestResourceStream(fullResourceName))
            {
                if (schemaStream != null)
                {
                    XmlSchema schema = XmlSchema.Read(schemaStream, null);
                    schemas.Add(schema);
                }
            }
            return schemas;
        }

        internal static string GetAppSettingsKeyValue(string key)
        {
            return System.Configuration.ConfigurationManager.AppSettings[key] ?? string.Empty;
        }

        internal static string GetConfigurationFileReferencePath(string basePath, Application application)
        {
            string filePath = string.Empty;

            var directories = new Stack<string>();
            directories.Push(basePath);

            while (directories.Count > 0)
            {
                string directory = directories.Pop();

                List<string> paths;
                if (application.Type == ApplicationType.Web)
                {
                    paths = new List<string>(1)
                                {
                                    string.Format(@"{0}\{1}\{2}",
                                                  directory,
                                                  application.ID,
                                                  ConfigurationFileName(application))
                                };
                }
                else
                {
                    paths = new List<string>(2)
                                         {
                                             string.Format(@"{0}\{1}\{2}",
                                                           directory,
                                                           application.ID,
                                                           ConfigurationFileName(application)),
                                             string.Format(@"{0}\{1}",
                                                           directory,
                                                           ConfigurationFileName(application))
                                         };
                }

                foreach (string path in paths)
                {
                    if (File.Exists(path))
                    {
                        filePath = path;
                    }
                    else
                    {
                        foreach (string dir in Directory.GetDirectories(directory))
                        {
                            if (!directories.Contains(dir)) directories.Push(dir);
                        }
                    }
                }
            }

            if (String.IsNullOrEmpty(filePath))
            {
                throw new FileNotFoundException(ConfigurationFileName(application), basePath, application.ID);
            }

            return filePath;
        }

        internal static List<string> GetFilesRecursively(string basePath, Application application)
        {
            var result = new List<string>();

            var directories = new Stack<string>();
            directories.Push(basePath);

            while (directories.Count > 0)
            {
                string directory = directories.Pop();
                string path = string.Format(@"{0}\{1}",
                                            directory,
                                            application.Type == ApplicationType.Exe
                                                ? ConfigurationFileName(application)
                                                : string.Format(@"{0}\{1}",
                                                                application.ID,
                                                                ConfigurationFileName(application)));

                if (File.Exists(path))
                {
                    result.Add(path);
                }

                string[] dirs = Directory.GetDirectories(directory);
                foreach (string dir in dirs)
                {
                    directories.Push(dir);
                }
            }

            return result;
        }

        internal static List<ConfigurationSetting> GetSettings(IEnumerable<ConfigurationSetting> configurationSettings,
                                                               HttpSection httpSection)
        {
            var settings =
                new List<ConfigurationSetting>(
                    from s in configurationSettings
                    where s.Name.ToLower() == Enum.GetName(typeof(HttpSection), httpSection).ToLower()
                    select s);

            return settings;
        }

        #endregion
    }
}